package com.gitee.wsl.ext.math

import com.gitee.wsl.base.Objects.hash
import kotlin.math.*


/**
 * 坐标系转换相关工具类，主流坐标系包括：<br></br>
 *
 *  * WGS84坐标系：即地球坐标系，中国外谷歌地图
 *  * GCJ02坐标系：即火星坐标系，高德、腾讯、阿里等使用
 *  * BD09坐标系：即百度坐标系，GCJ02坐标系经加密后的坐标系。百度、搜狗等使用
 *
 *
 *
 * 坐标转换相关参考: https://tool.lu/coordinate/<br></br>
 * 参考：https://github.com/JourWon/coordinate-transform
 *
 * @author hongzhe.qin(qin462328037at163.com), looly
 * @since 5.7.16
 */
object CoordinateUtil {
    /**
     * 坐标转换参数：(火星坐标系与百度坐标系转换的中间量)
     */
    val X_PI: Double = 3.1415926535897932384626433832795 * 3000.0 / 180.0

    /**
     * 坐标转换参数：π
     */
    //const val PI: Double = 3.1415926535897932384626433832795

    /**
     * 地球半径（Krasovsky 1940）
     */
    const val RADIUS: Double = 6378245.0

    /**
     * 修正参数（偏率ee）
     */
    const val CORRECTION_PARAM: Double = 0.00669342162296594323

    /**
     * 判断坐标是否在国外<br></br>
     * 火星坐标系 (GCJ-02)只对国内有效，国外无需转换
     *
     * @param lng 经度
     * @param lat 纬度
     * @return 坐标是否在国外
     */
    fun outOfChina(lng: Double, lat: Double): Boolean {
        return (lng < 72.004 || lng > 137.8347) || (lat < 0.8293 || lat > 55.8271)
    }

    //----------------------------------------------------------------------------------- WGS84
    /**
     * WGS84 转换为 火星坐标系 (GCJ-02)
     *
     * @param lng 经度值
     * @param lat 纬度值
     * @return 火星坐标 (GCJ-02)
     */
    fun wgs84ToGcj02(lng: Double, lat: Double): Coordinate {
        return Coordinate(lng, lat).offset(offset(lng, lat, true))
    }

    /**
     * WGS84 坐标转为 百度坐标系 (BD-09) 坐标
     *
     * @param lng 经度值
     * @param lat 纬度值
     * @return bd09 坐标
     */
    fun wgs84ToBd09(lng: Double, lat: Double): Coordinate {
        val gcj02 = wgs84ToGcj02(lng, lat)
        return gcj02ToBd09(gcj02.lng, gcj02.lat)
    }

    //----------------------------------------------------------------------------------- GCJ-02
    /**
     * 火星坐标系 (GCJ-02) 转换为 WGS84
     *
     * @param lng 经度坐标
     * @param lat 纬度坐标
     * @return WGS84 坐标
     */
    fun gcj02ToWgs84(lng: Double, lat: Double): Coordinate {
        return Coordinate(lng, lat).offset(offset(lng, lat, false))
    }

    /**
     * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换
     *
     * @param lng 经度值
     * @param lat 纬度值
     * @return BD-09 坐标
     */
    fun gcj02ToBd09(lng: Double, lat: Double): Coordinate {
        val z = sqrt(lng * lng + lat * lat) + 0.00002 * sin(lat * X_PI)
        val theta = atan2(lat, lng) + 0.000003 * cos(lng * X_PI)
        val bd_lng = z * cos(theta) + 0.0065
        val bd_lat = z * sin(theta) + 0.006
        return Coordinate(bd_lng, bd_lat)
    }

    //----------------------------------------------------------------------------------- BD-09
    /**
     * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换
     * 即 百度 转 谷歌、高德
     *
     * @param lng 经度值
     * @param lat 纬度值
     * @return GCJ-02 坐标
     */
    fun bd09ToGcj02(lng: Double, lat: Double): Coordinate {
        val x = lng - 0.0065
        val y = lat - 0.006
        val z = sqrt(x * x + y * y) - 0.00002 * sin(y * X_PI)
        val theta = atan2(y, x) - 0.000003 * cos(x * X_PI)
        val gg_lng = z * cos(theta)
        val gg_lat = z * sin(theta)
        return Coordinate(gg_lng, gg_lat)
    }

    /**
     * 百度坐标系 (BD-09) 与 WGS84 的转换
     *
     * @param lng 经度值
     * @param lat 纬度值
     * @return WGS84坐标
     */
    fun bd09toWgs84(lng: Double, lat: Double): Coordinate {
        val gcj02 = bd09ToGcj02(lng, lat)
        return gcj02ToWgs84(gcj02.lng, gcj02.lat)
    }

    /**
     * WGS84 坐标转为 墨卡托投影
     *
     * @param lng 经度值
     * @param lat 纬度值
     * @return 墨卡托投影
     */
    fun wgs84ToMercator(lng: Double, lat: Double): Coordinate {
        val x = lng * 20037508.342789244 / 180
        var y: Double = ln(tan((90 + lat) * PI / 360)) / (PI / 180)
        y = y * 20037508.342789244 / 180
        return Coordinate(x, y)
    }

    /**
     * 墨卡托投影 转为 WGS84 坐标
     *
     * @param mercatorX 墨卡托X坐标
     * @param mercatorY 墨卡托Y坐标
     * @return WGS84 坐标
     */
    fun mercatorToWgs84(mercatorX: Double, mercatorY: Double): Coordinate {
        val x = mercatorX / 20037508.342789244 * 180
        var y = mercatorY / 20037508.342789244 * 180
        y = 180 / PI * (2 * atan(exp(y * PI / 180)) - PI / 2)
        return Coordinate(x, y)
    }

    //----------------------------------------------------------------------------------- Private methods begin
    /**
     * WGS84 与 火星坐标系 (GCJ-02)转换的偏移算法（非精确）
     *
     * @param lng 经度值
     * @param lat 纬度值
     * @param isPlus 是否正向偏移：WGS84转GCJ-02使用正向，否则使用反向
     * @return 偏移坐标
     */
    private fun offset(lng: Double, lat: Double, isPlus: Boolean): Coordinate {
        var dlng = transLng(lng - 105.0, lat - 35.0)
        var dlat = transLat(lng - 105.0, lat - 35.0)

        var magic = sin(lat / 180.0 * PI)
        magic = 1 - CORRECTION_PARAM * magic * magic
        val sqrtMagic = sqrt(magic)

        dlng = (dlng * 180.0) / (RADIUS / sqrtMagic * cos(lat / 180.0 * PI) * PI)
        dlat = (dlat * 180.0) / ((RADIUS * (1 - CORRECTION_PARAM)) / (magic * sqrtMagic) * PI)

        if (!isPlus) {
            dlng = -dlng
            dlat = -dlat
        }

        return Coordinate(dlng, dlat)
    }

    /**
     * 计算经度坐标
     *
     * @param lng 经度坐标
     * @param lat 纬度坐标
     * @return ret 计算完成后的
     */
    private fun transLng(lng: Double, lat: Double): Double {
        var ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * sqrt(abs(lng))
        ret += (20.0 * sin(6.0 * lng * PI) + 20.0 * sin(2.0 * lng * PI)) * 2.0 / 3.0
        ret += (20.0 * sin(lng * PI) + 40.0 * sin(lng / 3.0 * PI)) * 2.0 / 3.0
        ret += (150.0 * sin(lng / 12.0 * PI) + 300.0 * sin(lng / 30.0 * PI)) * 2.0 / 3.0
        return ret
    }

    /**
     * 计算纬度坐标
     *
     * @param lng 经度
     * @param lat 纬度
     * @return ret 计算完成后的
     */
    private fun transLat(lng: Double, lat: Double): Double {
        var ret = (-100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * sqrt(abs(lng)))
        ret += (20.0 * sin(6.0 * lng * PI) + 20.0 * sin(2.0 * lng * PI)) * 2.0 / 3.0
        ret += (20.0 * sin(lat * PI) + 40.0 * sin(lat / 3.0 * PI)) * 2.0 / 3.0
        ret += (160.0 * sin(lat / 12.0 * PI) + 320 * sin(lat * PI / 30.0)) * 2.0 / 3.0
        return ret
    }

    //----------------------------------------------------------------------------------- Private methods end
    /**
     * 坐标经纬度
     *
     * @author looly
     */
    class Coordinate(
        /**
         * 经度
         */
        var lng: Double,
        /**
         * 纬度
         */
        var lat: Double,
    ) {
        /**
         * 获取经度
         *
         * @return 经度
         */
        /**
         * 获取纬度
         *
         * @return 纬度
         */

        /**
         * 设置经度
         *
         * @param lng 经度
         * @return this
         */
        fun setLng(lng: Double): Coordinate {
            this.lng = lng
            return this
        }

        /**
         * 设置纬度
         *
         * @param lat 纬度
         * @return this
         */
        fun setLat(lat: Double): Coordinate {
            this.lat = lat
            return this
        }

        /**
         * 当前坐标偏移指定坐标
         *
         * @param offset 偏移量
         * @return this
         */
        fun offset(offset: Coordinate): Coordinate {
            this.lng += offset.lng
            this.lat += offset.lat
            return this
        }

        override fun equals(o: Any?): Boolean {
            if (this === o) {
                return true
            }
            if (o == null || this::class != o::class) {
                return false
            }
            val that = o as Coordinate
            return that.lng == lng  && that.lat == lat
        }

        override fun hashCode(): Int {
            return hash(lng, lat)
        }

        override fun toString(): String {
            return "Coordinate{" +
                    "lng=" + lng +
                    ", lat=" + lat +
                    '}'
        }
        
    }
}